/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.loaders;
import java.beans.PropertyEditor;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.text.MessageFormat;
import java.lang.reflect.*;
import java.awt.datatransfer.Transferable;
import org.openide.filesystems.*;
import org.openide.filesystems.FileSystem;
import org.openide.util.NbBundle;
import org.openide.nodes.Node;
import org.openide.nodes.FilterNode;
import org.openide.nodes.NodeTransfer;
import org.openide.nodes.Sheet;
import org.openide.nodes.PropertySupport;
import org.openide.util.HelpCtx;
import org.openide.util.WeakListener;
/** Default implementation of a shortcut to another data object.
*
* @author Jan Jancura, Jaroslav Tulach
*/
public class DataShadow extends DataObject {
/** generated Serialized Version UID */
static final long serialVersionUID = 6305590675982925167L;
/** original data object */
private DataObject original;
/** Extension name. */
static final String SHADOW_EXTENSION = "shadow"; // NOI18N
/** Constructs new data shadow for given primary file and referenced original.
* Method to allow subclasses of data shadow.
*
* @param fo the primary file
* @param original original data object
* @param loader the loader that created the object
*/
protected DataShadow (
FileObject fo, DataObject original, DataLoader loader
) throws DataObjectExistsException {
super (fo, loader);
this.original = original;
}
/** Constructs new data shadow for given primary file and referenced original.
* @param fo the primary file
* @param original original data object
*/
private DataShadow (FileObject fo, DataObject original) throws DataObjectExistsException {
this (fo, original, DataLoaderPool.getShadowLoader ());
}
/** Method that creates new data shadow in a folder. The name chosen is based
* on the name of the original object.
*
* @param folder target folder to create data in
* @param original orignal object that should be represented by the shadow
*/
public static DataShadow create (DataFolder folder, DataObject original)
throws IOException {
return create (folder, null, original);
}
/** Method that creates new data shadow in a folder. All modifications are
* done atomicly using fileSystem.runAtomic.
*
* @param folder target folder to create data in
* @param name name to give to the shadow
* @param original orignal object that should be represented by the shadow
*/
public static DataShadow create (
DataFolder folder,
final String name,
final DataObject original
) throws IOException {
final FileObject fo = folder.getPrimaryFile ();
final DataShadow[] arr = new DataShadow[1];
fo.getFileSystem ().runAtomicAction (new FileSystem.AtomicAction () {
public void run () throws IOException {
String n;
if (name == null) {
n = FileUtil.findFreeFileName (fo, original.getName (), SHADOW_EXTENSION);
} else {
n = name;
}
FileObject file = writeOriginal (
fo.createData (n, SHADOW_EXTENSION), original
);
DataObject obj = DataObject.find (file);
if (obj instanceof DataShadow) {
arr[0] = (DataShadow)obj;
} else {
// wrong instance => shadow was not found
throw new DataObjectNotFoundException (obj.getPrimaryFile ());
}
}
});
return arr[0];
}
/** Writes the original into given file.
* @param fo file to write to
* @param original data object to store into
* @return the file
* @exception IOException on I/O error
*/
private static FileObject writeOriginal (FileObject fo, DataObject obj)
throws IOException {
FileLock lock = fo.lock ();
try {
Writer os = new OutputStreamWriter (fo.getOutputStream (lock));
FileObject pf = obj.getPrimaryFile ();
os.write (pf.getPackageNameExt ('/', '.'));
os.write ('\n');
os.write (pf.getFileSystem ().getSystemName ());
os.write ('\n');
os.close ();
} finally {
lock.releaseLock ();
}
return fo;
}
/** Loads proper dataShadow from the file fileObject.
*
* @param <CODE>FileObject fileObject</CODE> The file to deserialize shadow from.
* @param <CODE>DataLoader loader</CODE>
* @return the shadow in this object
* @exception IOException error during load
*/
static DataShadow deserialize (FileObject fileObject) throws java.io.IOException {
BufferedReader ois = new BufferedReader (new InputStreamReader (fileObject.getInputStream ()));
try {
String s = ois.readLine ();
String fs = ois.readLine ();
if (s == null) {
// not found
throw new java.io.FileNotFoundException (fileObject.getPackageNameExt ('/', '.'));
}
Repository rep = org.openide.TopManager.getDefault ().getRepository ();
FileSystem fileSystem;
if (fs != null) {
// try to locate the fs
fileSystem = rep.findFileSystem (fs);
} else {
fileSystem = null;
}
FileObject fo;
if (fileSystem != null) {
// first of all try to locate the shadow by file system
fo = fileSystem.findResource (s);
} else {
fo = null;
}
if (fo == null) {
fo = rep.findResource (s);
}
if (fo == null) {
throw new java.io.FileNotFoundException (s);
}
DataObject original = DataObject.find (fo);
return new DataShadow (fileObject, original);
} finally {
ois.close ();
}
}
/** Return the original shadowed object.
* @return the data object
*/
public DataObject getOriginal () {
return original;
}
/* Creates node delegate.
*/
protected Node createNodeDelegate () {
return new ShadowNode (this);
}
/* Getter for delete action.
* @return true if the object can be deleted
*/
public boolean isDeleteAllowed () {
return !getPrimaryFile ().isReadOnly ();
}
/* Getter for copy action.
* @return true if the object can be copied
*/
public boolean isCopyAllowed () {
return true;
}
/* Getter for move action.
* @return true if the object can be moved
*/
public boolean isMoveAllowed () {
return !getPrimaryFile ().isReadOnly ();
}
/* Getter for rename action.
* @return true if the object can be renamed
*/
public boolean isRenameAllowed () {
return !getPrimaryFile ().isReadOnly ();
}
/* Help context for this object.
* @return help context
*/
public HelpCtx getHelpCtx () {
return getOriginal ().getHelpCtx ();
}
/* Handles copy of the data object.
* @param f target folder
* @return the new data object
* @exception IOException if an error occures
*/
protected DataObject handleCopy (DataFolder f) throws IOException {
return handleCreateFromTemplate (f, getName ());
}
/* Deals with deleting of the object. Must be overriden in children.
* @exception IOException if an error occures
*/
protected void handleDelete () throws IOException {
FileLock lock = getPrimaryFile ().lock ();
try {
getPrimaryFile ().delete (lock);
} finally {
lock.releaseLock ();
}
}
/* Handles renaming of the object.
* Must be overriden in children.
*
* @param name name to rename the object to
* @return new primary file of the object
* @exception IOException if an error occures
*/
protected FileObject handleRename (String name) throws IOException {
FileLock lock = getPrimaryFile ().lock ();
try {
getPrimaryFile ().rename (lock, name, SHADOW_EXTENSION);
} finally {
lock.releaseLock ();
}
return getPrimaryFile ();
}
/* Handles move of the object. Must be overriden in children.
*
* @param f target data folder
* @return new primary file of the object
* @exception IOException if an error occures
*/
protected FileObject handleMove (DataFolder f) throws IOException {
String name = FileUtil.findFreeFileName (f.getPrimaryFile (), getName (), SHADOW_EXTENSION);
return FileUtil.moveFile (getPrimaryFile (), f.getPrimaryFile (), name);
}
/* Creates shadow for this object in specified folder. The current
* implementation creates reference data shadow and pastes it into
* specified folder.
*
* @param f the folder to create shortcut in
* @return the shadow
*/
protected DataShadow handleCreateShadow (DataFolder f) throws IOException {
return original.handleCreateShadow (f);
}
/* Handles creation of new data object from template. This method should
* copy content of the template to destination folder and assign new name
* to the new object.
*
* @param f data folder to create object in
* @param name name to give to the new object (or <CODE>null</CODE>
* if the name is up to the template
* @return new data object
* @exception IOException if an error occured
*/
protected DataObject handleCreateFromTemplate (
DataFolder f, String name
) throws IOException {
if (name == null) name = getName ();
name = FileUtil.findFreeFileName (f.getPrimaryFile (), name, SHADOW_EXTENSION);
return new DataShadow (
FileUtil.copyFile (getPrimaryFile (), f.getPrimaryFile (), name),
original
);
}
/* Scans the orginal bundle */
public Node.Cookie getCookie (Class c) {
if (c.isInstance (this)) {
return this;
}
return original.getCookie (c);
}
/** Node for a shadow object. */
protected static class ShadowNode extends FilterNode {
/** message to create name of node */
private static MessageFormat format;
/** message to create short description of node */
private static MessageFormat descriptionFormat;
/** shadow */
private DataShadow obj;
/** the sheet computed for this node or null */
private Sheet sheet;
/** Create a shadowing node.
* @param shadow the shadow
*/
public ShadowNode (DataShadow shadow) {
this (shadow, shadow.getOriginal ().getNodeDelegate ());
}
/** Initializes it */
private ShadowNode (DataShadow shadow, Node node) {
super (node);
this.obj = shadow;
}
/* Clones the node
*/
public Node cloneNode () {
ShadowNode sn = new ShadowNode (obj);
return sn;
}
/* Renames the shadow data object.
* @param name new name for the object
* @exception IllegalArgumentException if the rename failed
*/
public void setName (String name) {
try {
obj.rename (name);
fireDisplayNameChange (null, null);
fireNameChange (null, null);
} catch (IOException ex) {
throw new IllegalArgumentException (ex.getMessage ());
}
}
/** The name of the shadow.
* @return the name
*/
public String getName () {
return obj.getName ();
}
/* Creates name based on the original one.
*/
public String getDisplayName () {
if (format == null) {
format = new MessageFormat (NbBundle.getBundle (DataShadow.class).getString ("FMT_shadowName"));
}
return format.format (createArguments ());
}
/** Creates arguments for given shadow node */
private Object[] createArguments () {
return new Object[] {
obj.getName (), // name of the shadow
super.getDisplayName (), // name of original
systemNameOrFileName (obj.getPrimaryFile ()), // full name of file for shadow
systemNameOrFileName (obj.getOriginal ().getPrimaryFile ()) // full name of original file
};
}
/** System name of file name
*/
private static String systemNameOrFileName (FileObject fo) {
if (fo.isRoot ()) {
try {
return fo.getFileSystem ().getDisplayName ();
} catch (FileStateInvalidException ex) {
}
}
return fo.getPackageNameExt ('/', '.');
}
/* Creates description based on the original one.
*/
public String getShortDescription () {
if (descriptionFormat == null) {
descriptionFormat = new MessageFormat (
NbBundle.getBundle (DataShadow.class).getString ("FMT_shadowHint")
);
}
return descriptionFormat.format (createArguments ());
}
/* @return obj.isDeleteAllowed () */
public boolean canDestroy () {
return obj.isDeleteAllowed ();
}
/* Destroyes the node
*/
public void destroy () throws IOException {
obj.delete ();
// super.destroy ();
}
/** @return true if shadow can be renamed
*/
public final boolean canRename () {
return obj.isRenameAllowed ();
}
/* Returns true if this object allows copying.
* @returns true if this object allows copying.
*/
public final boolean canCopy () {
return obj.isCopyAllowed ();
}
/* Returns true if this object allows cutting.
* @returns true if this object allows cutting.
*/
public final boolean canCut () {
return obj.isMoveAllowed ();
}
/* First of all the DataObject.getCookie method is
* called. If it produces non-null result, it is returned.
* Otherwise the value returned from super.getCookie
* method is returned.
*
* @return the cookie or null
*/
public Node.Cookie getCookie (Class cl) {
Node.Cookie c = obj.getCookie (cl);
if (c != null) {
return c;
} else {
return super.getCookie (cl);
}
}
/** Returns modified properties of the original node.
* @return property sets
*/
public PropertySet[] getPropertySets () {
Sheet s = sheet;
if (s == null) {
s = sheet = cloneSheet ();
}
return s.toArray ();
}
/** Copy this node to the clipboard.
*
* @return {@link ExTransferable.Single} with one flavor, {@link NodeTransfer#nodeCopyFlavor}
* @throws IOException if it could not copy
*/
public Transferable clipboardCopy () throws IOException {
return NodeTransfer.transferable (this, NodeTransfer.CLIPBOARD_COPY);
}
/** Cut this node to the clipboard.
*
* @return {@link ExTransferable.Single} with one flavor, {@link NodeTransfer#nodeCopyFlavor}
* @throws IOException if it could not cut
*/
public Transferable clipboardCut () throws IOException {
return NodeTransfer.transferable (this, NodeTransfer.CLIPBOARD_CUT);
}
/**
* This implementation only calls clipboardCopy supposing that
* copy to clipboard and copy by d'n'd are similar.
*
* @return transferable to represent this node during a drag
* @exception IOException when the
* cut cannot be performed
*/
public Transferable drag () throws IOException {
return clipboardCopy ();
}
/** Creates a node listener that allows listening on the
* original node and propagating events to the proxy.
* <p>Intended for overriding by subclasses, as with {@link #createPropertyChangeListener}.
*
* @return a {@link NodeAdapter} in the default implementation
*/
protected org.openide.nodes.NodeListener createNodeListener () {
return new PropL (this);
}
/** Equal if the o is ShadowNode to the same shadow object.
*/
public boolean equals (Object o) {
if (o instanceof ShadowNode) {
ShadowNode sn = (ShadowNode)o;
return sn.obj == obj;
}
return false;
}
/** Hashcode is computed by the represented shadow.
*/
public int hashCode () {
return obj.hashCode ();
}
/** Clones the property sheet of original node.
*/
private Sheet cloneSheet () {
PropertySet[] sets = this.getOriginal ().getPropertySets ();
Sheet s = new Sheet ();
for (int i = 0; i < sets.length; i++) {
Sheet.Set ss = new Sheet.Set ();
ss.put (sets[i].getProperties ());
ss.setName (sets[i].getName ());
ss.setDisplayName (sets[i].getDisplayName ());
ss.setShortDescription (sets[i].getShortDescription ());
// modifies the set if it contains name of object property
modifySheetSet (ss);
s.put (ss);
}
return s;
}
/** Modifies the sheet set to contain name of property and name of
* original object.
*/
private void modifySheetSet (Sheet.Set ss) {
Property p = ss.remove (DataObject.PROP_NAME);
if (p != null) {
p = new PropertySupport.Name (this);
ss.put (p);
p = new Name ();
ss.put (p);
}
}
/** Class that renames the orginal object and also updates
* the link
*/
private final class Name extends PropertySupport.ReadWrite {
public Name () {
super (
"OriginalName", // NOI18N
String.class,
DataObject.getString ("PROP_ShadowOriginalName"),
DataObject.getString ("HINT_ShadowOriginalName")
);
}
public Object getValue () {
return obj.getOriginal ().getName();
}
public void setValue (Object val) throws IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
if (!canWrite())
throw new IllegalAccessException();
if (!(val instanceof String))
throw new IllegalArgumentException();
try {
DataObject orig = obj.getOriginal ();
orig.rename ((String)val);
writeOriginal (obj.getPrimaryFile (), orig);
} catch (IOException ex) {
throw new InvocationTargetException (ex);
}
}
public boolean canWrite () {
return obj.getOriginal ().isRenameAllowed();
}
}
/** Property listener on data object that delegates all changes of
* properties to this node.
*/
private static class PropL extends FilterNode.NodeAdapter {
public PropL (ShadowNode sn) {
super (sn);
}
/* JST PENDING: Does not compile with oldjavac => ignore it, it not too necessary neither not too much
* data objects are changing property sets, but uncomment when we switch to new javac.
*
protected void propertyChange (FilterNode fn, PropertyChangeEvent ev) {
if (ev.getPropertyName ().equals (Node.PROP_PROPERTY_SETS)) {
// clear the sheet
ShadowNode sn = (ShadowNode)fn;
sn.sheet = null;
}
super.propertyChange (fn, ev);
}
*/
}
}
}
/*
* Log
* 11 Gandalf 1.10 1/16/00 Jaroslav Tulach Compiles with oldjavac
* but does not work properly.
* 10 Gandalf 1.9 1/15/00 Jaroslav Tulach #5285
* 9 Gandalf 1.8 1/15/00 Jaroslav Tulach DataShadow enhancements
* 8 Gandalf 1.7 1/12/00 Ian Formanek NOI18N
* 7 Gandalf 1.6 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 6 Gandalf 1.5 10/5/99 Jaroslav Tulach DataShadow.create
* 5 Gandalf 1.4 9/3/99 Jaroslav Tulach #3649
* 4 Gandalf 1.3 9/2/99 Jaroslav Tulach fire(Display)NameChange
* 3 Gandalf 1.2 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 2 Gandalf 1.1 5/14/99 Martin Ryzl BUG #1623
* 1 Gandalf 1.0 3/26/99 Ian Formanek
* $
*/